package com.azunitech.guice;

import java.lang.reflect.Method;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.PersistenceConfiguration;
import net.sf.ehcache.config.PersistenceConfiguration.Strategy;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Logger;

public class CacheInterceptor implements MethodInterceptor{
	private static Logger logger = Logger.getLogger(CacheInterceptor.class);
	private int maxEntriesLocalHeap = 5000;
	private CacheManager manager = CacheManager.create();
	
	public static final int NO_PARAM_KEY = 0;
	public static final int NULL_PARAM_KEY = 53;
	
	public CacheInterceptor(){
		logger.info("Create EHcacheInterceptor");
		manager = CacheManager.create();
	}
	
	public Object invoke(MethodInvocation methodInvocation) throws Throwable {
		Cached annotation = methodInvocation.getMethod().getAnnotation(Cached.class);
		String cacheName = annotation.cacheName();
		
		Cache cache = manager.getCache(cacheName);
		if ( cache == null){
			cache = new Cache(
					new CacheConfiguration(cacheName, maxEntriesLocalHeap)
					.memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)
					.eternal(false)
					.timeToLiveSeconds(60)
					.timeToIdleSeconds(30)
					.diskExpiryThreadIntervalSeconds(0)
					.persistence(new PersistenceConfiguration().strategy(Strategy.LOCALTEMPSWAP)));
			manager.addCache(cache);	
		}
		
		return getResultFromCacheOrMethod(methodInvocation, cache);
	}
	
	private Object getResultFromCacheOrMethod(MethodInvocation methodInvocation, Cache cache) throws Throwable{
		Object key = generate(methodInvocation.getThis().getClass(), methodInvocation.getMethod(), methodInvocation.getArguments());
		Element element = cache.get(key);
		if ( element == null){
			logger.info("Cannot find in EHCache, invoke target.");
			Object val = methodInvocation.proceed();
			element = new Element(key, val);
			cache.put(element);
			logger.info("Get Result from Target and put in Cache.");
		}
		logger.info("Return result to caller");
        return element.getObjectValue();
	}
	
	private Object generate(Object target, Method method, Object... params) {
	    if (params.length == 1) {
	        return (params[0] == null ? NULL_PARAM_KEY : params[0]);
	    }
	    if (params.length == 0) {
	        return NO_PARAM_KEY;
	    }
	    int hashCode = 17;
	    for (Object object : params) {
	        hashCode = 31 * hashCode + (object == null ? NULL_PARAM_KEY : object.hashCode());
	    }
	    return Integer.valueOf(hashCode);
	}
}
